package com.work.demo.utils;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.SM4;
import com.google.common.primitives.Bytes;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

import static com.work.demo.utils.CryptoConst.ALGO_AES;
import static com.work.demo.utils.CryptoConst.ALGO_RSA;
import static com.work.demo.utils.CryptoConst.ENC_AES_GCM;
import static com.work.demo.utils.CryptoConst.KE_UTF8;

/**
 * 加解密工具类
 * <p>
 * 未引用二方包 scplatform-common-2.0.0-SNAPSHOT
 * 但是和二方包{org.unitedata.scplatform.common.utils.EncryptUtil} 算法保持一致
 * </p>
 *
 * 基本规范：
 * <ol>
 *   <li>方法命名风格为 {@code {algo}Encrypt/Decrypt}，如 {@code aesEncrypt}</li>
 *   <li>对称加密的密钥为 {@code secretKey}，非对称加密的密钥对为 {@code publicKey/privateKey}</li>
 *   <li>明文字符串为 {@code plainText}，密文字符串为 {@code cipherText}</li>
 *   <li>加密后的字节数组通过 Base64 编码为密文字符串，使用时注意编解码</li>
 * </ol>
 *
 * @author yedu
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EncryptUtil {

    private static final int GCM_TAG_LEN = 16;
    private static final int GCM_IV_LEN = 12;
    public static final String KE_BASE64 = "BASE64";

    private static final String IV = "ZkR_SiNoSOFT=568";

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    // AES ---------------------------------------------------------------

    /**
     * AES加密
     *
     * @param plainText 待加密的内容
     * @param secretKey 密钥，Base64 String
     * @return 加密后的 Base64 String
     */
    public static String aesEncrypt(String plainText, String secretKey) throws GeneralSecurityException {
        return aesEncrypt(plainText, secretKey, KE_BASE64);
    }

    public static String aesEncrypt(String plainText, String secretKey, String keyEncoding) throws GeneralSecurityException {
        if (secretKey == null || secretKey.isEmpty()) {
            throw new IllegalArgumentException("No AES secret key provided");
        }
        keyEncoding = StringUtils.defaultIfBlank(keyEncoding, KE_BASE64);
        byte[] secretKeyBytes = decode(secretKey, keyEncoding);

        // 使用随机的初始向量，提升安全性
        byte[] iv = new byte[GCM_IV_LEN];
        new SecureRandom().nextBytes(iv);

        Cipher cipher = initAesCipher(Cipher.ENCRYPT_MODE, secretKeyBytes, iv);
        byte[] encrypted = Bytes.concat(iv, cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)));
        return base64Encode(encrypted);
    }

    /**
     * AES解密
     *
     * @param cipherText 待解密的内容，Base64 String
     * @param secretKey  密钥，Base64 String
     * @return 解密后的原始 String
     */
    public static String aesDecrypt(String cipherText, String secretKey) throws GeneralSecurityException {
        return aesDecrypt(cipherText, secretKey, KE_BASE64);
    }

    public static String aesDecrypt(String cipherText, String secretKey, String keyEncoding) throws GeneralSecurityException {
        if (secretKey == null || secretKey.isEmpty()) {
            throw new IllegalArgumentException("No AES secret key provided");
        }
        keyEncoding = StringUtils.defaultIfBlank(keyEncoding, KE_BASE64);
        byte[] secretKeyBytes = decode(secretKey, keyEncoding);

        byte[] encrypted = base64Decode(cipherText);
        byte[] iv = Arrays.copyOfRange(encrypted, 0, GCM_IV_LEN);
        Cipher cipher = initAesCipher(Cipher.DECRYPT_MODE, secretKeyBytes, iv);
        return new String(cipher.doFinal(encrypted, GCM_IV_LEN, encrypted.length - GCM_IV_LEN), StandardCharsets.UTF_8);
    }

    /**
     * 加解密通用逻辑，GCM 的密文结构为 iv + cipher
     */
    private static Cipher initAesCipher(int mode, byte[] secretKey, byte[] initVector) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(secretKey, ALGO_AES);
            GCMParameterSpec paramSpec = new GCMParameterSpec(GCM_TAG_LEN * Byte.SIZE, initVector);
            Cipher cipher = Cipher.getInstance(ENC_AES_GCM);
            cipher.init(mode, keySpec, paramSpec);
            return cipher;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    // RSA ---------------------------------------------------------------

    /**
     * RSA 公钥加密，使用 X.509 标准
     *
     * @param plainText 待加密的内容
     * @param publicKey 公钥，Base64 String
     * @return 加密后的 Base64 String
     */
    public static String rsaEncrypt(String plainText, String publicKey) throws GeneralSecurityException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64Decode(publicKey));
        KeyFactory factory = KeyFactory.getInstance(ALGO_RSA);
        PublicKey pubKey = factory.generatePublic(keySpec);

        Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        byte[] bytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        return base64Encode(bytes);
    }

    /**
     * RSA 私钥解密，使用 PKCS#8 标准
     *
     * @param cipherText 待解密的内容，Base64 String
     * @param privateKey 私钥，Base64 String
     * @return 解密后的原始 String
     */
    public static String rsaDecrypt(String cipherText, String privateKey) throws GeneralSecurityException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(base64Decode(privateKey));
        KeyFactory factory = KeyFactory.getInstance(ALGO_RSA);
        PrivateKey priKey = factory.generatePrivate(keySpec);

        Cipher cipher = Cipher.getInstance(factory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        byte[] bytes = cipher.doFinal(base64Decode(cipherText));
        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * 校验公私钥是否配对
     */
    public static void checkRsaKeys(String privateKey, String publicKey) throws GeneralSecurityException {
        String text = RandomStringUtils.random(10);
        String encrypted = rsaEncrypt(text, publicKey);
        if (!text.equals(rsaDecrypt(encrypted, privateKey))) {
            throw new IllegalArgumentException("Illegal RSA keys");
        }
    }

    // SM2 ---------------------------------------------------------------

    /**
     * SM2 公钥加密
     *
     * @param plainText
     * @param publicKey Base64 String
     * @return Base64 String
     */
    public static String sm2Encrypt(String plainText, String publicKey) {
        byte[] encrypted = SmUtil.sm2(null, publicKey)
                .encrypt(plainText.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
        return base64Encode(encrypted);
    }

    /**
     * SM2 私钥加密
     *
     * @param cipherText Base64 String
     * @param privateKey Base64 String
     * @return
     */
    public static String sm2Decrypt(String cipherText, String privateKey) {
        byte[] decrypted = SmUtil.sm2(privateKey, null)
                .decrypt(base64Decode(cipherText), KeyType.PrivateKey);
        return new String(decrypted, StandardCharsets.UTF_8);
    }

    // helper ---------------------------------------------------------------

    private static byte[] decode(String key, String encoding) {
        switch (encoding) {
            case KE_BASE64:
                return base64Decode(key);
            case KE_UTF8:
                // 兼容遗留编码类型
                return key.getBytes(StandardCharsets.UTF_8);
            default:
                throw new UnsupportedOperationException("Unsupported key encoding: " + encoding);
        }
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    private static byte[] base64Decode(String base64Str) {
        return StringUtils.isEmpty(base64Str) ? ArrayUtils.EMPTY_BYTE_ARRAY : Base64.decodeBase64(base64Str);
    }


    /**
     * 国密4加密为base 64 code
     *
     * @param content 待加密的内容
     * @param key     加密密钥
     * @param iv      偏移量
     * @return 加密后的base 64 code
     */
    public static String sm4Encrypt(String content, String key, String iv) {
        if (StrUtil.isBlank(iv)){
            iv = IV;
        }

        SM4 sm4 = new SM4(Mode.CBC, Padding.ZeroPadding, key.getBytes(), iv.getBytes());
        return sm4.encryptBase64(content);
    }

    public static String sm4Decrypt(String encryptStr, String key, String iv) {
        if (StrUtil.isBlank(iv)){
            iv = IV;
        }

        SM4 sm4 = new SM4(Mode.CBC, Padding.ZeroPadding, key.getBytes(), iv.getBytes());
        return sm4.decryptStr(encryptStr, CharsetUtil.CHARSET_UTF_8);
    }

}